#    Supsta
#    Copyright (c) 2014 Sebastian Dufner
#
#    This file is part of Supsta.
#
#    Supsta is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    Supsta is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.

require 'rexml/document'
require 'yaml'

module Supsta
  module ApplicationUtils
    N_Textures_X, N_Textures_Y = 16, 16
    Overlay_Filename, Overlay_Inds = 'overlays.png', [4, 6, 8, 10, 12, 14, 68]
    Textures_Filename_Pattern = 't%03d.png'
    Normals_Filename_Pattern = 't%03d_NRM.png'
    Texture_Type_Texture = 'tex'
    Texture_Type_Normal = 'norm'
    Block_Texture_Filename_Pattern = "#{Texture_Type_Texture}_%d.png"
    Block_Texture_Filename_Pattern2 = "%d_#{Texture_Type_Texture}_%d.png"
    Block_Normal_Filename_Pattern = "#{Texture_Type_Normal}_%d.png"
    Block_Normal_Filename_Pattern2 = "%d_#{Texture_Type_Normal}_%d.png"
    Exts = ['png', 'xcf', 'psd']
    Block_Regex = /\A(?:(?<specialized>\d+)_)?(?<type>(?:#{Texture_Type_Texture}|#{Texture_Type_Normal}))_(?<frame>\d+)\.(?<ext>#{Exts.join '|'})\z/

  protected

    def add_texture(dir, size, num_sides, color, animated = false, specialized = false)
      texture = @gimp.new_image(size, size)
      normal = @gimp.new_image(size, size)

      texture.select.all.fill(*color.map {|c| (c * 255).to_i})
      normal.select.all.fill(127, 127, 255)

      speed = @fast ? :fast_rgba : :best_compression
      num_sides = 4 if animated

      num_sides.times do |i|
        if specialized
          file = File::join(
                dir, Block_Texture_Filename_Pattern2 % [specialized, i])
        else
          file = File::join(dir, Block_Texture_Filename_Pattern % i)
        end
        if File::exists?(file) && !@force
          warn "File #{file} exists, won't overwrite unless --force specified"
        else
          @gimp.puts "Creating texture: #{file}"
          texture.save(file)
        end

        if specialized
          file = File::join(
                dir, Block_Normal_Filename_Pattern2 % [specialized, i])
        else
          file = File::join(dir, Block_Normal_Filename_Pattern % i)
        end
        if File::exists?(file) && !@force
          warn "File #{file} exists, won't overwrite unless --force specified"
        else
          @gimp.puts "Creating normal map: #{file}"
          normal.save(file)
        end
      end

      texture.delete!
      normal.delete!
    end

    def compile_texture(filename, options = {})
      @gimp.message "compiling texture: #{filename}"

      return unless filename.end_with? '.png'
      unless @textures
        @textures = {}
        @config[:sizes].each {|tpsize| @textures[tpsize] = {}}
      end

      texture_file = File::basename filename
      match = texture_file.match Block_Regex
      return unless match
      specialization = match[:specialized]
      specialization = specialization.to_i if specialization

      dir = File::dirname filename
      block_name = File::basename dir
      texture_config = read_texture_conf block_name
      specializations = texture_config[:specialized]
      offset_x, offset_y = texture_config[:x_offset], texture_config[:y_offset]

      image_index, x, y =
          texture_index(texture_config[:id] + match[:frame].to_i)
      texture = @gimp.load_image(filename).convert_rgb
      layer = texture.merge_visible_layers

      if block_name.start_with? Overlay_Filename
        return unless match[:type] == Texture_Type_Texture
        output_file = Overlay_Filename
      elsif match[:type] == Texture_Type_Texture
        output_file = Textures_Filename_Pattern % image_index
      elsif match[:type] == Texture_Type_Normal
        output_file = Normals_Filename_Pattern % image_index

        texture.set_component_state(:green, :inactive)
            .set_component_state(:blue, :inactive).select.all.invert
        texture.set_component_state(:green, :active)
            .set_component_state(:blue, :active)
      end

      output_hash = @textures[output_file]
      if specialization
        texture.select.all.copy!
        output_hash[specialization].active_layer.paste
            .scale(specialization, specialization)
            .set_offsets(x * specialization, y * specialization)
            .anchor!
      else
        @config[:sizes].each do |size|
          texture.select.rectangle(
                offset_x, offset_y,
                @config[:workingsize], @config[:workingsize]).copy!
          output_hash[size]
              .select.rectangle(x * size, y * size, size, size).clear.paste
              .scale(size, size).set_offsets(x * size, y * size).anchor!
        end
      end
      texture.delete!
    end

    def texture_index(pos)
      return pos / N_Textures_X / N_Textures_Y,
          pos % N_Textures_X,
          pos / N_Textures_Y % N_Textures_Y
    end

    def load_config(filename = 'supsta.yml')
      path = File::join(project_path, filename)
      exit_now!("This is no #{Supsta::Program} project!") unless
        File::exists? path

      config = YAML.load_file(path)
      @files = config[:files]
      @config =
          {
            workingsize: config[:workingsize],
            sizes: config[:sizes],
            starmade: config[:starmade]
          }
    end

    def save_config(filename = 'supsta.yml')
      File::open(File::join(project_path, filename), 'w') do |file|
        file << @config.merge(files: @files).to_yaml
      end
    end

    def save_texture_conf(block_name, config, filename = 'texture.yml')
      File::open(File::join(source_path, block_name, filename), 'w') do |file|
        file << config.to_yaml
      end
    end

    def read_texture_conf(block_name, filename = 'texture.yml')
      filename = File::join(source_path, block_name, filename)
      YAML.load_file(filename) if File::exists? filename
    end

    def list_files
      Dir["#{source_path}/**/*.{#{Exts.join ','}}"]
    end

    def load_compilations
      @gimp.message "Loading previously compiled textures..."
      @textures = {}

      @config[:sizes].each do |size|
        Dir::foreach(File::join(compile_path, size.to_s)) do |filename|
          if filename =~ /\.png\z/
            textures = (@textures[filename] ||= {})
            textures[size.to_i] =
                @gimp.load_image(File::join(compile_path, size.to_s, filename))
          end
        end
      end

      @gimp.message "Done."
      @textures
    end

    def modified_files
      modfiles = {}

      list_files.each do |file|
        mtime = File::mtime(file)
        modfiles[file] = mtime unless mtime == @files[file]
      end

      modfiles
    end

    def create_compilations
      Dir[File::join(sm_originals_path, '*.png')].each do |original_file|
        original = @gimp.load_image original_file

        @config[:sizes].each do |size|
          width, height = N_Textures_X * size, N_Textures_Y * size
          image = @gimp.new_image(width, height)

          mkdir File::join(compile_path, size.to_s)

          original.select.all.copy!
          image.active_layer.paste.scale(width, height).set_offsets(0, 0)
              .anchor!
          image.save(File::join(
                    compile_path, size.to_s, File::basename(original_file)))
              .delete!
        end

        original.delete!
      end
    end

  private

    def block_config
      return @block_config if @block_config

      print 'Reading block config...'
      filename =
          File::join(@config[:starmade], 'data', 'config', 'BlockConfig.xml')
      conf = {}
      file = REXML::Document.new(File.open(filename))

      REXML::XPath::each(file, '//Block') do |block|
        id = block.attributes['textureId'].to_i
        next if conf[id]

        name = block.attributes['name']
        num_frames, color, animated = nil, nil, nil

        block.each do |child|
          next unless child.is_a? REXML::Element

          if child.name == 'Animated'
            animated = child.text =~ /true/
          elsif child.name == 'IndividualSides'
            num_frames = child.text.to_i
          elsif child.name == 'LightSourceColor'
            color = child.text.split(/,/).map &:to_f
          end
        end

        conf[id] =
            {
              id: id,
              name: name,
              numsides: num_frames,
              color: color,
              animated: !!animated
            }
      end

      puts ' Done.'

      @block_config = conf
    end

    def mkdir(dir)
      Dir::mkdir(dir) unless Dir::exists?(dir)
    end

    def validate_sm_dir!
      dir = @config[:starmade]

      exit_now!("directory is not a StarMade directory: #{dir}") unless
        Dir::exists?(dir) && Dir::exists?(sm_originals_path) &&
            File::exists?(sm_config_path)
    end

    def do_upgrade
      block_config.values.each do |block|
        if conf = read_texture_conf(block[:name])
          conf.merge! block
          save_texture_conf(block[:name], conf)
        else
          add(block[:name], project_path, block)
        end
      end if @config[:starmade]
    end

  end
end
